home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Applications / Workspace / Briefcase / Source / MultApp.m < prev    next >
Text File  |  1992-08-10  |  25KB  |  889 lines

  1. #import "MultApp.h"
  2. #import "MultDoc.h"
  3. #import "Help.h"
  4. #import "PrefDelegate.h"
  5. #import "PageMargin.h"
  6. #import <appkit/appkit.h>
  7.  
  8. @implementation MultApp:Object
  9.  
  10. /* Canon Information Systems is not responsible for anything anyone does with this   */
  11. /* code, nor are they responsible for the correctness of this code.  Basically, this */
  12. /* has very little to do with the company I work for, and you can't blame them.      */
  13.  
  14. /* This file is best read in a window as wide as this comment, and with tab settings */
  15. /* of 4 spaces and indent setting of 4 spaces (at least, that's what I use).         */
  16.  
  17. /* You are welcome to do as you would with this file under the following conditions. */
  18. /* First, I accept no blame for anything that goes wrong no matter how you use it,   */
  19. /* no matter how catastrophic, not even if it stems from a bug in my code.             */
  20. /* Second, please keep my notices on it when/if you distribute it.                     */
  21. /* Third, if you discover any bugs or have any comments, PLEASE TELL ME!  Code won't */
  22. /* get better without people picking it apart and giving the writer feedback.        */
  23. /* Fourth, if you modify it, please keep a notice that your version is based on mine */
  24. /* in the source files (and keep the notice that mine is based on four other pieces  */
  25. /* of code :<).  Thanks, and have fun.  - Subrata Sircar, ssircar@canon.com             */
  26.  
  27. /* This class is intended as an Application Delegate.  As such it */
  28. /* is supposed to provide the document-management functionality   */
  29. /* that a multiple-document, drag-and-drop application should be  */
  30. /* able to do.  This includes keeping track of the current thing, */
  31. /* managing the shared panels, updating the defaults and menus,   */
  32. /* and controlling communication with the Workspace Manager.      */
  33.  
  34. /* The paradigm used is that the application delegate relays new messages */
  35. /* to the factory Document class, and every other doc message goes to     */
  36. /* the first responder.  The delegate opens nib sections containing       */
  37. /* the info and preferences panel (the prefs panel should have a delegate */
  38. /* to update it) and lets the helpObject worry about itself.  Otherwise   */
  39. /* it acts on delegate messages.                                          */
  40.  
  41. /* Source code stolen from Draw, Acceptor, and WhatsUpDoc.  Thanks!       */
  42. /* Source code also taken from Ernest Prabhakar's BasicApp example, which */
  43. /* includes more methods than mine does (for things like services, etc.)  */
  44. /* His examples are well worth checking out, especially if you need more  */
  45. /* functionality than this class can provide.                              */
  46. /* - Subrata Sircar            ssircar@canon.com    Canon Information Systems */
  47.  
  48. /* Version 0.9b            Apr-19-92        First Public Release    */
  49. /* Version 0.95b        Jun-10-92        Minor File path fixes    */
  50. /* Version 1.0b            Aug-10-92        Minor Bug Fixes            */
  51.  
  52. /* Preprocessor macros */
  53.  
  54. #define DocClass        [[[self class] docClass] class]
  55. #define DocExtension    [DocClass extension]
  56. #define theVersion        ((float)([[self class] version])/100.0)
  57. #define marginDefault    "36.0"
  58.  
  59. /* Factory (Class) Variables */
  60. /* These are declared as static internal because I felt that all members    */
  61. /* of this class would use the same values for these variables.  Subclasses    */
  62. /* can only manipulate these variables through the provided class methods.     */
  63. /* BE VERY CAREFUL about overriding methods which depends on the class        */
  64. /* variables without calling the super method - it might cause problems.    */
  65.  
  66. static id              docClass;                /* Class of Document to use         */
  67.  
  68. static const char    *theAppName;            /* Cache the appName for later use    */
  69. static const char    *noValue;                /* Cache the local no and yes        */
  70. static const char    *yesValue;
  71.  
  72. static const char     *VersionFormat;            /* Version Format String            */
  73. static const int     myVersion         = 100;    /* Version multiplied by 100        */
  74. BOOL                 InMsgPrint         = NO;
  75.  
  76. /* Factory (Class) Methods */
  77.  
  78. + initialize
  79. /* Class variable initialization.  DO NOT call from subclasses. */
  80. {
  81.     if (self == [MultApp class]) {
  82.         [self setVersion:myVersion];
  83.         [self setDocClass:[MultDoc class]];
  84.         VersionFormat = NXCopyStringBufferFromZone(LocalString("Version %.2f"), MyZone);
  85.         theAppName = NXCopyStringBufferFromZone([NXApp appName],MyZone);
  86.         noValue = NXCopyStringBufferFromZone(LocalString("NO"),MyZone);
  87.         yesValue = NXCopyStringBufferFromZone(LocalString("YES"),MyZone);
  88.     }
  89.     return self;
  90. }
  91.  
  92. + setDocClass:newDoc
  93. /* Sets the document class and the file extensions to be used.  Requires    */
  94. /* the document class to implement a class method to return the extension    */
  95. /* and the file types.                                                        */
  96. {
  97.     docClass = newDoc;
  98.     return self;
  99. }
  100.  
  101. + docClass
  102. {
  103.     return docClass;
  104. }
  105.  
  106. /* Private C functions used to implement methods in this class. */
  107.  
  108. static void initMenu(id menu)
  109. /*
  110.  * Sets the updateAction for every menu item which sends to the
  111.  * First Responder (i.e. their target is nil).  When autoupdate is on,
  112.  * every event will be followed by an update of each of the menu items
  113.  * which is visible.  This keep all unavailable menu items dimmed out
  114.  * so that the user knows what options are available at any given time.
  115.  */ 
  116. {
  117.     int count;
  118.     id matrix, cell;
  119.     id matrixTarget, cellTarget;
  120.  
  121.     matrix = [menu itemList];
  122.     matrixTarget = [matrix target];
  123.  
  124.     count = [matrix cellCount];
  125.     while (count--) {
  126.         cell = [matrix cellAt:count :0];
  127.         cellTarget = [cell target];
  128.         if (!matrixTarget && !cellTarget) {
  129.             [cell setUpdateAction:@selector(menuItemUpdate:) forMenu:menu];
  130.         } else if ([cell hasSubmenu]) {
  131.             initMenu(cellTarget);
  132.         } else if (cellTarget == [NXApp delegate])
  133.             [cell setUpdateAction:@selector(menuItemUpdate:) forMenu:menu];
  134.     }
  135. }
  136.  
  137. static id documentInWindow(id window)
  138. /*
  139.  * Checks to see if the passed window's delegate is a document.
  140.  * If it is, it returns that document, otherwise it returns nil.
  141.  */
  142. {
  143.     id document = [window delegate];
  144.     if (document) return [document isKindOf:docClass] ? document : nil;
  145.     else return nil;
  146. }
  147.  
  148. static id findDocument(const char *name)
  149. /*
  150.  * Searches the window list looking for a document with the specified name.
  151.  * Returns the window containing the document if found.
  152.  * If name == NULL then the first document found is returned.
  153.  */
  154. {
  155.     int count;
  156.     id document, window, windowList;
  157.  
  158.     windowList = [NXApp windowList];
  159.     count = [windowList count];
  160.     while (count--) {
  161.         window = [windowList objectAt:count];
  162.         document = documentInWindow(window);
  163.         if (document && (!name || !strcmp([document fileName], name))) return window;
  164.     }
  165.  
  166.     return nil;
  167. }
  168.  
  169. static id openFile(const char *directory, const char *name, BOOL display)
  170. /*
  171.  * Opens a file with the given name in the specified directory.
  172.  * If we already have that file open, it is ordered front.
  173.  * Returns the document if successful, nil otherwise.
  174.  */
  175. {
  176.     id window;
  177.     char buffer[MAXPATHLEN+1], path[MAXPATHLEN+1];
  178.  
  179.     if (name && *name) {
  180.         if (!directory) directory = ".";
  181.         else if (*directory != '/') {
  182.             strcpy(buffer, "./");
  183.             strcat(buffer, directory);
  184.             directory = buffer;
  185.         }
  186.         if (!chdir(directory) && getwd(path)) {
  187.             strcat(path, "/");
  188.             strcat(path, name);
  189.             window = findDocument(path);
  190.             if (window) {
  191.                 if (display) [window makeKeyAndOrderFront:window];
  192.                 return [window delegate];
  193.             } else return [docClass newFromFile:path];
  194.         } else Notify(LocalString("Open:  path invalid"), directory);
  195.     }
  196.  
  197.     return nil;
  198. }
  199.  
  200.  
  201. /* Public Methods */
  202.  
  203. - currentDocument
  204. {
  205.     return documentInWindow([NXApp mainWindow]);
  206. }
  207.  
  208. - (const char *)currentDirectory
  209. /* Returns the current document's directory */
  210. {
  211.     const char *retval = [[self currentDocument] directory];
  212.     if (!retval || !*retval) retval = defaultDir;
  213.     return retval;
  214. }
  215.  
  216. - setDefaultDir:(const char *)dir
  217. {
  218.     sprintf(defaultDir,"%s",dir);
  219.     return self;
  220. }
  221.  
  222. - (const char *)launchDirectory
  223. {
  224.     return launchDir;
  225. }
  226.  
  227.  
  228. /* Default support methods */
  229. // These functions manage the app's behavior when it quits with unsaved documents
  230.  
  231. - (BOOL)saveAll
  232. {
  233.     return saveAll;
  234. }
  235.  
  236. - setSaveAll:(BOOL)value
  237. {
  238.     saveAll = value;
  239.     return self;
  240. }
  241.  
  242. - (BOOL)dumpAll
  243. {
  244.     return dumpAll;
  245. }
  246.  
  247. - setDumpAll:(BOOL)value
  248. {
  249.     dumpAll = value;
  250.     return self;
  251. }
  252.  
  253.  
  254. /* Shared Panels */
  255.  
  256. - info:sender
  257. /*
  258.  * Returns the information Panel if it hasn't already been loaded.
  259.  */
  260. {
  261.     id versionField;    /* version field in info panel */
  262.     char buf[20];
  263.     if (!infoPanel) {
  264.         NXZone *zone = NXCreateChildZone(MyZone, vm_page_size, vm_page_size, YES);
  265.         NXNameZone(zone,"infoPanel");
  266.         LoadLocalNib(LocalString("Info.nib"),self,YES,zone);
  267.         versionField = NXGetNamedObject("VersionNumber", infoPanel);
  268.         if (versionField) {
  269.             sprintf(buf, VersionFormat,theVersion);
  270.             [versionField setStringValue:buf];
  271.         }
  272.         NXMergeZone(zone);
  273.     }
  274.     [infoPanel orderFront:self];
  275.     return infoPanel;
  276. }
  277.  
  278. - pref:sender
  279. /*
  280.  * The preferences panel is a separate nib module and only loaded on demand.
  281.  * When loaded, the object that manages the panel is asked to update it.
  282.  */
  283. {
  284.     if (!prefPanel) {
  285.         NXZone *zone = NXCreateChildZone(MyZone, vm_page_size, vm_page_size, YES);
  286.         LoadLocalNib(LocalString("Pref.nib"),self,NO,zone);
  287.         NXMergeZone(zone);
  288.     }
  289.     [[prefPanel delegate] load:self];
  290.     [prefPanel orderFront:self];
  291.     return prefPanel;
  292. }
  293.  
  294. - saveAsPanel:sender
  295. {
  296.     id savepanel = [SavePanel new];
  297.     [savepanel setAccessoryView:nil];
  298.     [savepanel setRequiredFileType:DocExtension];
  299.     return savepanel;
  300. }
  301.  
  302. - pageLayout:sender
  303. /*
  304.  * Returns the application-wide PageLayout panel, with margins.
  305.  */
  306. {
  307.     return pageMargin;
  308. }
  309.  
  310. - stringSet:sender
  311. /*
  312.  * Returns the application-wide string table, if it exists.
  313.  */
  314. {
  315.     if (stringSet) return stringSet;
  316.     else return nil;
  317. }
  318.  
  319. - help:sender
  320. {
  321.     if (!helpPanel) {
  322.         NXZone *zone = NXCreateChildZone(MyZone, vm_page_size, vm_page_size, YES);
  323.         LoadLocalNib(LocalString("Help.nib"),self,NO,zone);
  324.         NXMergeZone(zone);
  325.     }
  326.     [[helpPanel delegate] generalHelp:self];
  327.     [helpPanel orderFront:self];
  328.     return helpPanel;
  329. }
  330.  
  331.  
  332. /* Target/Action Methods */
  333.  
  334. - new:sender
  335. /*
  336.  * Called by pressing New in the Document menu.
  337.  */
  338. {
  339.     [[docClass class] new];
  340.     return self;
  341. }
  342.  
  343. - open:sender
  344. /*
  345.  * Called by pressing Open... in the Document menu.
  346.  */
  347. {
  348.     const char *directory;
  349.     const char *const *files;
  350.     const char *const myType[2] = {DocExtension, NULL};
  351.     id openpanel = [[OpenPanel new] allowMultipleFiles:YES];
  352.  
  353.     directory = [self currentDirectory];
  354.     if (directory && (*directory == '/')) [openpanel setDirectory:directory];
  355.     if ([openpanel runModalForTypes:myType]) {
  356.         files = [openpanel filenames];
  357.         directory = [openpanel directory];
  358.         while (files && *files) {
  359.             haveOpenedDocument = openFile(directory, *files, YES) || haveOpenedDocument;
  360.             files++;
  361.         }
  362.     }
  363.  
  364.     return self;
  365. }
  366.  
  367. - saveAll:sender
  368. /*
  369.  * Saves all the documents.
  370.  */
  371. {
  372.     int count;
  373.     id windowList;
  374.  
  375.     windowList = [NXApp windowList];
  376.     count = [windowList count];
  377.     while (count--) {
  378.         [documentInWindow([windowList objectAt:count]) save:self];
  379.     }
  380.  
  381.     return self;
  382. }
  383.  
  384. - print:sender
  385. {
  386.     return [[[self currentDocument] view] printPSCode:sender];
  387. }
  388.  
  389. - mailToMe:sender
  390. // Stolen from Opener 3.0.  Thanks, Michael!
  391. {
  392.     char subj[256], w[256] = "whoami";
  393.     char body[4096]="\
  394. Subrata:\n\n\
  395. Great source code!  It runs like beauty in the night...\n\
  396. BUT!  I do have a few comments:\n\n\
  397.    <insert accolades, criticisms & suggestions here>\n\n\
  398.              Sincerely,\n\
  399.              ";
  400. #define call(a,b) [s performRemoteMethod:a with:b length:strlen(b)+1]
  401.     id s = [NXApp appSpeaker];
  402.     port_t mailPort = NXPortFromName("Mail", NULL); // make sure app is launched
  403.  
  404.     mailPort = NXPortFromName("MailSendDemo",NULL);
  405.     if (mailPort == PORT_NULL) {
  406.         Notify(LocalString("Suggestion attempt failed with Missing Port to Mail"),LocalString("Check if Mail is running."));
  407.         return self;
  408.     }
  409.     [s setSendPort:mailPort];
  410.  
  411.     sprintf(subj,"Comments and suggestions for ``%s'', Version %.2f ",theAppName,theVersion);
  412.     strcat(body,execstr(w)); strcat(body,"\n");
  413.     call("setTo:","ssircar@canon.com");
  414.     call("setSubject:",subj);
  415.     call("setBody:",body);
  416.     
  417.     return self;
  418. }
  419.  
  420.  
  421. /* Automatic update methods */
  422.  
  423. - (BOOL)menuItemUpdate:menuCell
  424. /*
  425.  * Method called by all menu items which send their actions to the
  426.  * First Responder.  First, if the object which would respond if the
  427.  * action was sent down the responder chain also responds to the message
  428.  * validateCommand:, then it is sent validateCommand: to determine
  429.  * whether that command is valid now; otherwise, if there is a responder
  430.  * to the message, then it is assumed that the item is valid.
  431.  * The method returns YES if the cell has changed its appearance (so that
  432.  * the caller (a Menu) knows to redraw it).
  433.  * This method also traps menu items sending to this class, and sets the
  434.  * action the same way.
  435.  */
  436. {
  437.     SEL action;
  438.     id responder, target;
  439.     BOOL enable;
  440.  
  441.     target = [menuCell target];
  442.     enable = [menuCell isEnabled];
  443.  
  444.     if (!target) {
  445.         action = [menuCell action];
  446.         responder = [NXApp calcTargetForAction:action];
  447.         if ([responder respondsTo:@selector(validateCommand:)]) {
  448.             enable = [responder validateCommand:menuCell];
  449.         } else enable = responder ? YES : NO;
  450.     } else if (target == self) enable = [self validateCommand:menuCell];
  451.  
  452.     if ([menuCell isEnabled] != enable) {
  453.         [menuCell setEnabled:enable];
  454.         return YES;
  455.     } else return NO;
  456. }
  457.  
  458. - (BOOL)validateCommand:menuCell
  459. /* The only messages the delegate is asked to validate are SaveAll and Print. */
  460. {
  461.     SEL action = [menuCell action];
  462.  
  463.     if ((action == @selector(saveAll:)) || (action == @selector(print:))) return findDocument(NULL) ? YES : NO;
  464.  
  465.     return YES;
  466. }
  467.  
  468. - setupPageLayout:(float)lm :(float)rm :(float)tm :(float)bm 
  469. {
  470.     if (!pageMargin) {
  471.         pageMargin = [PageMargin new];
  472.         [pageMargin setPlpAccessory:plpAccessory];
  473.     }
  474.     [pageMargin setValues:lm right:rm top:tm bottom:bm];
  475.     [pageMargin writePrintInfo];
  476.     return self;
  477. }
  478.  
  479. /* Application Delegate Methods */
  480.  
  481. - appWillInit:sender
  482. {
  483.     char temp[MAXPATHLEN+1];
  484.     float lm,rm,tm,bm;
  485.  
  486. #ifndef DEBUG            /* Don't Dump Core if not Debugging */
  487.     struct rlimit rl={ 0, 0};
  488.     getrlimit( RLIMIT_CORE, &rl);
  489.     rl.rlim_cur=0;
  490.     setrlimit( RLIMIT_CORE, &rl);
  491. #endif
  492.  
  493.     /* Check and load the application defaults.  The basics are the quit */
  494.     /* behavior and the page margins.  Also sets the menu updating flag. */
  495.     
  496.     if (!NXGetDefaultValue(theAppName,LocalString("SaveAll"))) sprintf(temp,noValue);
  497.     else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("SaveAll")));
  498.     if (!strcmp(temp,yesValue)) saveAll = YES;
  499.     if (!NXGetDefaultValue(theAppName,LocalString("DumpAll"))) sprintf(temp,noValue);
  500.     else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("DumpAll")));
  501.     if (!strcmp(temp,yesValue)) dumpAll = YES;
  502.     
  503.     if (!NXGetDefaultValue(theAppName,LocalString("LeftMargin"))) sprintf(temp,marginDefault);
  504.     else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("LeftMargin")));
  505.     sscanf(temp,"%f",&lm);
  506.     if (!NXGetDefaultValue(theAppName,LocalString("RightMargin"))) sprintf(temp,marginDefault);
  507.     else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("RightMargin")));
  508.     sscanf(temp,"%f",&rm);
  509.     if (!NXGetDefaultValue(theAppName,LocalString("TopMargin"))) sprintf(temp,marginDefault);
  510.     else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("TopMargin")));
  511.     sscanf(temp,"%f",&tm);
  512.     if (!NXGetDefaultValue(theAppName,LocalString("BottomMargin"))) sprintf(temp,marginDefault);
  513.     else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("BottomMargin")));
  514.     sscanf(temp,"%f",&bm);
  515.     
  516.     [self setupPageLayout:lm :rm :tm :bm];
  517.     
  518.     [NXApp setAutoupdate:YES];
  519.  
  520.     return self;
  521. }
  522.  
  523. - appDidInit:sender
  524. /*
  525.  * Register our window with the Workspace for icon dropping.
  526.  * Check for files to open specified on the command line.
  527.  * Initialize the menus and check if we were opened to print or quit.
  528.  * If there are no open documents, then open blank documents.
  529.  */
  530. {
  531.     int         i;
  532.     char         buffer[MAXPATHLEN+1], temp[MAXPATHLEN+1];
  533.     char         *directory, *name, *ext;
  534.     u_int        windowNum;
  535.     id             speaker;
  536.     
  537.     /* register our app icon window with the workspace */
  538.     NXConvertWinNumToGlobal([[NXApp appIcon] windowNum], &windowNum);
  539.     speaker = [NXApp appSpeaker];
  540.     [speaker setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
  541.     [speaker registerWindow:windowNum
  542.              toPort:[[NXApp appListener] listenPort]];
  543.  
  544.     getAppDirectory(launchDir);
  545.     [self setDefaultDir:NXHomeDirectory()];
  546.     
  547.     /* Check for command line files to open */
  548.     if (NXArgc > 1) {
  549.         for (i = 1; i < NXArgc; i++) {
  550.             strcpy(buffer, NXArgv[i]);
  551.             ext = rindex(buffer, '.');
  552.             if (!ext || strcmp(++ext, DocExtension)) {
  553.                 strcat(buffer,".");
  554.                 strcat(buffer, DocExtension);
  555.             }
  556.             if (*buffer == '/') {
  557.                 directory = "/";
  558.                 name = buffer;
  559.                 name++;
  560.             } else {
  561.                 name = rindex(buffer, '/');
  562.                 if (name) {
  563.                     *name++ = '\0';
  564.                     sprintf(temp,"%s/%s",launchDir,buffer);
  565.                     directory = temp;
  566.                 } else {
  567.                     name = buffer;
  568.                     directory = NULL;
  569.                 }
  570.             }
  571.             haveOpenedDocument = openFile(directory, name, YES) || haveOpenedDocument;
  572.         }
  573.     }
  574.  
  575.     if (!haveOpenedDocument) [self new:self];    /* if none opened, open one */
  576.     if (NXGetDefaultValue(theAppName,LocalString("Quit"))) {
  577.         [NXApp activateSelf:YES];
  578.         NXPing();
  579.         [NXApp terminate:self];
  580.     }
  581.  
  582.     initMenu([NXApp mainMenu]);
  583.  
  584.     return self;
  585. }
  586.  
  587. - appWillTerminate:sender
  588. /*
  589.  * Overridden to be sure all documents get an opportunity to be saved
  590.  * before exiting the program.  Save the defaults too.
  591.  */
  592. {
  593.     int count, choice;
  594.     float lm,rm,tm,bm;
  595.     char temp[100];
  596.     id window, document, windowList;
  597.  
  598.     windowList = [NXApp windowList];
  599.     count = [windowList count];
  600.     [pageMargin getValues:&lm right:&rm top:&tm bottom:&bm];
  601.     sprintf(temp,"%f",lm);
  602.     NXWriteDefault([NXApp appName],LocalString("LeftMargin"),temp);
  603.     sprintf(temp,"%f",rm);
  604.     NXWriteDefault([NXApp appName],LocalString("RightMargin"),temp);
  605.     sprintf(temp,"%f",tm);
  606.     NXWriteDefault([NXApp appName],LocalString("TopMargin"),temp);
  607.     sprintf(temp,"%f",bm);
  608.     NXWriteDefault([NXApp appName],LocalString("BottomMargin"),temp);
  609.     if (saveAll) {
  610.         while (count--) {
  611.             document = [[windowList objectAt:count] delegate];
  612.             if ([document respondsTo:@selector(needsSaving)] && [document needsSaving]) {
  613.                 [document save:self];
  614.             }
  615.         }
  616.         NXWriteDefault(theAppName,LocalString("SaveAll"),yesValue);
  617.         NXWriteDefault(theAppName,LocalString("DumpAll"),noValue);
  618.     } else if (dumpAll) {
  619.         NXWriteDefault(theAppName,LocalString("DumpAll"),yesValue);
  620.         NXWriteDefault(theAppName,LocalString("SaveAll"),noValue);
  621.     } else {
  622.         NXWriteDefault(theAppName,LocalString("DumpAll"),noValue);
  623.         NXWriteDefault(theAppName,LocalString("SaveAll"),noValue);
  624.         while (count--) {
  625.             window = [windowList objectAt:count];
  626.             document = [window delegate];
  627.             if ([document respondsTo:@selector(needsSaving)] && [document needsSaving]) {
  628.                 choice = NXRunAlertPanel(LocalString("Quit"),
  629.                                          LocalString("You have unsaved documents."),
  630.                                          LocalString("Review Unsaved"),
  631.                                          LocalString("Quit Anyway"),
  632.                                          LocalString("Cancel"));
  633.                 if (choice == NX_ALERTOTHER) return nil;
  634.                 else if (choice == NX_ALERTALTERNATE) return self;
  635.                 else if (choice == NX_ALERTDEFAULT) {
  636.                     count = 0;
  637.                     choice = [windowList count];
  638.                     while (choice--) {
  639.                         window = [windowList objectAt:choice];
  640.                         document = [window delegate];
  641.                         if ([document respondsTo:@selector(windowWillClose:)]) {
  642.                             if ([document windowWillClose:self]) [window close];
  643.                             else return nil;
  644.                         }
  645.                     }
  646.                 }
  647.             }
  648.         }
  649.     }
  650.  
  651.     return self;
  652. }
  653.     
  654. - (int)appOpenFile:(const char *)path type:(const char *)type
  655. /*
  656.  * This method is performed whenever a user double-clicks on an icon
  657.  * in the Workspace Manager representing a app program document.
  658.  */
  659. {
  660.     char *name;
  661.     char directory[MAXPATHLEN+1];
  662.  
  663.     if (type && !strcmp(type, DocExtension)) {
  664.         strcpy(directory, path);
  665.         name = rindex(directory, '/');
  666.         if (name) {
  667. #ifdef DEBUG
  668.             printf("Directory %s: File %s\n",directory,name);
  669. #endif
  670.             if (name != index(directory, '/')) {
  671.                 *name++ = '\0';
  672.                 if (openFile(directory, name, YES)) {
  673.                     haveOpenedDocument = YES;
  674.                     return YES;
  675.                 }
  676.             } else {
  677.                 name++;
  678.                 if (openFile("/", name, YES)) {
  679.                     haveOpenedDocument = YES;
  680.                     return YES;
  681.                 }
  682.             }
  683. #ifdef DEBUG
  684.             printf("After Open, File is %s\n",name);
  685. #endif
  686.         }
  687.     }
  688.  
  689.     return NO;
  690. }
  691.  
  692. - (BOOL)appAcceptsAnotherFile:sender
  693. /*
  694.  * We accept any number of appOpenFile:type: messages.
  695.  */
  696. {
  697.     return YES;
  698. }
  699.  
  700. - (int)app:sender unmounting:(const char *)fullPath
  701. {
  702.     id    windowList    = [NXApp windowList];
  703.     int    count        = [windowList count];
  704.     id    document    = NULL;
  705.     
  706.     /* if any of our documents are in that path, close them and notify the user */
  707.  
  708.     while (count--) {
  709.         document = documentInWindow([windowList objectAt:count]);
  710.         if (document && (!strncmp([document directory],fullPath,strlen(fullPath)))) {
  711.             Notify(fullPath,LocalString("is being unmounted; documents here will close."));
  712.         /* Use performClose so that the window delegate will be notified. */
  713.             [[document window] performClose:self];
  714.         }
  715.         
  716.     }
  717.     /* the current directory is wherever our current document is, so we're all set */
  718.     return 0;
  719. }        
  720.  
  721.  
  722. /* Listener/Speaker remote methods */
  723.  
  724. // IconDragging Stuff, oh boy, oh boy!
  725. static char **filePath = NULL;
  726. static int files = 0;
  727.  
  728. - (int)iconEntered:(int)windowNum at:(double)x :(double)y
  729.     iconWindow:(int)iconWindowNum iconX:(double)iconX iconY:(double)iconY
  730.     iconWidth:(double)iconWidth iconHeight:(double)iconHeight
  731.     pathList:(char *)pathList
  732. {
  733.     char *stringPos, *tempPtr;
  734.     char tempBuf[MAXPATHLEN+1];
  735.     int count = 0;
  736.     
  737.     if (filePath) {
  738.         freeList(filePath);
  739.         filePath = NULL;
  740.     }
  741.     stringPos = tempPtr = pathList;
  742.     files = 0;
  743.         
  744.     /* This code just parses the pathList for the filenames and explictly */
  745.     /* null-terminates them after copying into a buffer.  It appends that */
  746.     /* buffer to the dynamically-allocated filelist which keeps track of it */
  747.  
  748.     /* the number of tabs + 1 equals the number of files dragged in */
  749.  
  750.     while (stringPos = index(stringPos, '\t')) {
  751.         count = (int)(stringPos-tempPtr);
  752.         strncpy(tempBuf,tempPtr,count);
  753.         *(tempBuf+count) = '\0';
  754.         filePath = addFile(tempBuf,filePath,files,MyZone);
  755.         files++;
  756.         stringPos++;
  757.         tempPtr=stringPos;
  758.     }
  759.     strncpy(tempBuf,tempPtr,strlen(tempPtr));
  760.     *(tempBuf+strlen(tempPtr)) = '\0';
  761.     filePath = addFile(tempBuf,filePath,files,MyZone);
  762.     files++;
  763.  
  764.     return 0;
  765. }
  766.  
  767. - (int)iconExitedAt:(double)x :(double)y
  768. // Throw away the files that were just waved in our faces
  769. {
  770.     freeList(filePath);
  771.     filePath = NULL;
  772.     files = 0;
  773.     return 0;
  774. }
  775.  
  776. - (int)iconReleasedAt:(double)x :(double)y ok:(int *)flag
  777. // try to open the files in FileList
  778. {    
  779.     char *strPtr;
  780.     
  781.     /* accept the icon */
  782.     *flag = 1;
  783.    
  784.     NX_DURING
  785.     while (files--) {
  786.         strPtr = rindex(filePath[files],'/');
  787.         if (strPtr != index(filePath[files],'/')) {
  788.             *strPtr++ = '\0';
  789.             openFile(filePath[files],strPtr,YES);
  790.         } else {
  791.             strPtr++;
  792.             openFile("/",strPtr,YES);
  793.         }
  794.     }
  795.     NX_HANDLER
  796.     NX_ENDHANDLER
  797.         
  798.     return 0;
  799. }
  800.  
  801. // Filename and path methods
  802. - (int)msgDirectory:(const char **)fullPath ok:(int *)flag
  803. {
  804.     *fullPath = [self currentDirectory];
  805.     if (*fullPath) *flag = YES;
  806.     else {
  807.         *fullPath = "";
  808.         *flag = NO;
  809.     }
  810.     return 0;
  811. }
  812.  
  813. - (int)msgFile:(const char **)fullPath ok:(int *)flag
  814. {
  815.     const char *file;
  816.  
  817.     file = [[self currentDocument] fileName];
  818.     if (file) {
  819.         *fullPath = file;
  820.         *flag = YES;
  821.     } else {
  822.         *fullPath = "";
  823.         *flag = NO;
  824.     }
  825.  
  826.     return 0;
  827. }
  828.  
  829. // Miscellaneous messages we might want to handle
  830. - (int)msgPrint:(const char *)fullPath ok:(int *)flag
  831. {
  832.     BOOL close;
  833.     id document = nil;
  834.     char *directory, *name;
  835.     char path[MAXPATHLEN+1];
  836.     char buffer[MAXPATHLEN+1];
  837.  
  838.     InMsgPrint = YES;
  839.     strcpy(buffer, fullPath);
  840.     name = rindex(buffer, '/');
  841.     if (name) {
  842.         *name++ = '\0';
  843.         directory = buffer;
  844.     } else {
  845.         name = buffer;
  846.         directory = NULL;
  847.     }
  848.     if (!chdir(directory) && getwd(path)) {
  849.         strcat(path, "/");
  850.         strcat(path, name);
  851.         document = [findDocument(path) delegate];
  852.     }
  853.     if (document) close = NO;
  854.     else {
  855.         document = openFile(directory, name, NO);
  856.         if (document) haveOpenedDocument = YES;
  857.         close = YES;
  858.     }
  859.     if (document && ![[document view] isEmpty]) {
  860.         [NXApp setPrintInfo:[document printInfo]];
  861.         [[document view] printPSCode:self];
  862.         if (close) [[[document view] window] close];
  863.         *flag = YES;
  864.     } else *flag = NO;
  865.     InMsgPrint = NO;
  866.  
  867.     return 0;
  868. }
  869.  
  870. - (int)msgVersion:(const char **)aString ok:(int *)flag
  871. {
  872.     char buf[20];
  873.  
  874.     sprintf(buf, VersionFormat, theVersion);
  875.     *aString = NXCopyStringBuffer(buf);
  876.     *flag = YES;
  877.  
  878.     return 0;
  879. }
  880.  
  881. - (int)msgQuit:(int *)flag
  882. {
  883.     *flag = ([NXApp terminate:self] ? NO : YES);
  884.     return 0;
  885. }
  886.  
  887. @end
  888.  
  889.